﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;

namespace Microsoft.WowAddonStudio.FrameXml.Formats.Blp
{
    public class BlpFile
    {
        private const int BlpFourCC = 0x32504c42; // B, L, P, 2

        public ReadOnlyCollection<MipMap> MipMaps { get; private set; }

        /// <summary>
        /// Gets the mipmap that contains the original image.
        /// </summary>
        public MipMap OriginalImageMipMap
        {
            get
            {
                if (MipMaps != null && MipMaps.Count > 0)
                    return MipMaps[0];

                return null;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlpFile"/> class.
        /// </summary>
        /// <param name="filename">The filename.</param>
        public BlpFile(string filename)
        {
            if (filename == null)
                throw new ArgumentNullException("filename");

            using (Stream stream = File.Open(filename, FileMode.Open, FileAccess.Read))
                this.Read(stream);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlpFile"/> class.
        /// </summary>
        /// <param name="stream">The stream.</param>
        public BlpFile(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            this.Read(stream);
        }

        private void Read(Stream stream)
        {
            // Read the BLP header
            BlpHeader header = BlpFile.ReadHeader(stream);

            // Read the mip maps
            MipMaps = BlpFile.ReadMips(header, stream);
        }

        private static ReadOnlyCollection<MipMap> ReadMips(BlpHeader header, Stream stream)
        {
            var maps = new List<MipMap>(header.Offsets.Length);

            int width = header.Width;
            int height = header.Height;

            for (int i = 0; i < header.Offsets.Length; i++)
            {
                stream.Position = header.Offsets[i];

                var data = new byte[header.Lengths[i]];
                stream.Read(data, 0, data.Length);

                switch (header.Compression)
                {
                    case BlpCompression.DXT1:
                    case BlpCompression.DXT3:
                    case BlpCompression.DXT5:
                    case BlpCompression.Uncompressed:
                        maps.Add(new DxtMipMap(new Size(width, height), data, header.Compression));
                        break;
                    default:
                        throw new NotImplementedException();
                }

                if (width == 1 || height == 1 || !header.HasMips)
                    break;

                width = width / 2;
                height = height / 2;
            }

            return maps.AsReadOnly();
        }

        private static BlpHeader ReadHeader(Stream stream)
        {
            var reader = new BinaryReader(stream);

            int fourCC = reader.ReadInt32();

            if (fourCC != BlpFourCC)
                throw new InvalidOperationException();

            int type = reader.ReadInt32();
            byte encoding = reader.ReadByte();
            byte alphaDepth = reader.ReadByte();
            byte alphaEncoding = reader.ReadByte();

            bool hasMips = reader.ReadByte() == 1;

            int width = reader.ReadInt32();
            int height = reader.ReadInt32();

            var offsets = new int[16];
            var lengths = new int[16];

            for (int i = 0; i < 16; i++)
                offsets[i] = reader.ReadInt32();

            for (int i = 0; i < 16; i++)
                lengths[i] = reader.ReadInt32();

            byte[] palette = reader.ReadBytes(256);

            return new BlpHeader
            {
                Compression = GetCompression(type, encoding, alphaDepth, alphaEncoding),
                Height = height,
                Width = width,
                Palette = palette,
                Offsets = offsets,
                Lengths = lengths,
                HasMips = hasMips
            };
        }

        private static BlpCompression GetCompression(int type, byte encoding, byte alphaDepth, byte alphaEncoding)
        {
            switch (type)
            {
                case 0:
                    throw new NotImplementedException();
                case 1:
                    switch (encoding)
                    {
                        case 1:
							throw new NotImplementedException(String.Format("BlpCompression with encoding:{0}  is not implemented!", encoding));
                        case 2:
                            if (alphaDepth == 0 || alphaDepth == 1)
                                return BlpCompression.DXT1;

                            if (alphaDepth == 8)
                            {
                                if (alphaEncoding == 1)
                                    return BlpCompression.DXT3;
                                if (alphaEncoding == 7)
                                    return BlpCompression.DXT5;
                            }

                            if (alphaDepth == 4)
                                return BlpCompression.Uncompressed;
                            break;
                    }
                    break;
            }

            throw new NotImplementedException();
        }
    }
}
